/************************************************************************
Copyright (c) 2010, 2013, C.R. All rights reserved. 

E-mail: crtoyou@gmail.com; crtoyou@163.com

You can redistribute it and/or modify it under the terms of the
GNU Lesser General Public License as published by the
Free Software Foundation version 3 of the License.

This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. if not, write to the Free Software Foundation, Inc., 
51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
************************************************************************/

#ifndef __CR_BYTE_STREAM_H__
#define __CR_BYTE_STREAM_H__

#include <crdefine.h>
#include <crdebug.h>
#include <tchar.h>

namespace cr {

class istream_base;
class ostream_base;

class stream_base 
{
private:
	stream_base(const stream_base&);
	stream_base& operator = (const stream_base&);

	friend void iostransfer(ostream_base& os, istream_base& is, size_t len);

public:
	enum errcode { eos }; // end of stream

	stream_base()
		: m_org(NULL)
		, m_end(NULL)
		, m_pos(NULL)
	{}

	~stream_base()
	{}

	inline void bind(byte* org, size_t len)
	{
		m_org = m_pos = org;
		m_end = org + len;
	}

	void seek(size_t offset)
	{
		ASSERT(isvalid());
		m_pos += offset;
		long over = m_pos - m_end;
		if (over > 0) {
			m_pos = m_end;
			if(!flush())
				throw exception<stream_base>(eos, this);
			seek(over);
		}
	}

public:
	inline void seek_org() { m_pos = m_org; }
	inline void seek_end() { m_pos = m_end; }
	inline size_t occupy() const { return m_pos - m_org; }
	inline size_t remain() const { return m_end - m_pos; }
	inline byte* tell() const { return m_pos; }
	inline bool iseos() const { return m_pos == m_end; }
	inline bool isvalid() const { return m_org <= m_pos && m_pos <= m_end; }

public:
	virtual bool flush() { return false; }

protected:
	byte* m_org;
	byte* m_end;
	byte* m_pos;

};

class istream_base : public stream_base
{
public:
	inline byte getbyte()
	{
		if(iseos() && !flush())
			throw exception<stream_base>(eos, this);
		ASSERT(isvalid() && !iseos());
		return *m_pos++;
	}

	inline uint16 getword()
	{
		uint16 word = getbyte();
		return word |= getbyte() << 8;
	}

	inline uint32 getuint()
	{
		uint32 uint = getword();
		return uint |= getword() << 16;
	}

	void read(byte* buf, size_t len)
	{
		if (!len)
			return;
		if (iseos() && !flush())
			throw exception<stream_base>(eos, this);
		ASSERT(isvalid() && !iseos());
		size_t space = m_end - m_pos;
		if (space < len) {
			memcpy_s(buf, m_end-m_pos, m_pos, space);
			m_pos = m_end;
			read(buf + space, len - space);
		}
		else {
			memcpy_s(buf, m_end - m_pos, m_pos, len);
			m_pos += len;
		}
	}

};

class ostream_base : public stream_base
{
public:
	inline void putbyte(byte ch)
	{
		if(iseos() && !flush())
			throw exception<stream_base>(eos, this);
		ASSERT(isvalid() && !iseos());
		*m_pos++ = ch;
	}

	inline void putword(uint16 word)
	{
		putbyte(word & 0xFF);
		putbyte(word >> 8);
	}

	inline void putuint(uint32 uint)
	{
		putword(uint & 0xFFFF);
		putword(uint >> 16);
	}

	void write(const byte* buf, size_t len)
	{
		if (!len)
			return;
		if (iseos() && !flush())
			throw exception<stream_base>(eos, this);
		ASSERT(isvalid() && !iseos());
		size_t space = m_end - m_pos;
		if (space < len) {
			memcpy_s(m_pos, m_end - m_pos, buf, space);
			m_pos = m_end;
			write(buf + space, len - space);
		}
		else {
			memcpy_s(m_pos, m_end - m_pos, buf, len);
			m_pos += len;
		}
	}

};

inline void iostransfer(ostream_base& os, istream_base& is, size_t len)
{
	size_t space_i = is.m_end - is.m_pos;
	if (space_i > len) {
		os.write(is.m_pos, space_i);
		is.m_pos = is.m_end;
		is.flush();
		iostransfer(os, is, len - space_i);
	}
	else {
		os.write(is.m_pos, len);
		is.m_pos += len;
	}
}

}//namespace

#endif //__CR_BYTE_STREAM_H__